import com.oracle.webservices.internal.api.databinding.DatabindingMode;
import lombok.Data;
import sun.security.krb5.internal.TGSRep;

import java.util.Random;
import java.util.concurrent.Callable;

/**
 * 主机类，模拟主机
 */
@Data
public class Host implements Runnable {
    private int id;
    private int successCount;
    private int conflictCount;
    private final Bus bus;

    public Host(int id, Bus bus) {
        this.id = id;
        this.bus = bus;
        this.conflictCount=16;
    }


    @Override
    public void run() {
//        1️⃣ 准备发送。但在发送之前，必须先检测信道。
        while (successCount!=10){


//          2️⃣   检测信道。若检测到信道忙，则应不停地检测，一直等待信道转为空闲。若检测到信道空闲  ，并在 96比特时间内信道保持空闲（保证了帧间最小间隔），就发送这个帧。
            while (bus.getBusData()!=0){
                try {
                    Thread.sleep( (new Random().nextInt(10)*5));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //发出信息
            synchronized (bus){

                int busData = bus.getBusData();
                bus.setBusData(busData|id);
            }
            //            3️⃣ 检查碰撞。在发送过程中仍不停地检测信道，即网络适配器要边发送边监听。这里只有两种可能性：

//✅ 发送成功：在争用期内一直未检测到碰撞。这个帧肯定能够发送成功。发送完毕后，其他什么也不做。然后回到 1️⃣。
            if (bus.getBusData()==id){
                //如果成功次数+1
                successCount++;
                //如果成功，需要重置冲突次数
                conflictCount=16;
                System.out.println("ID:"+Thread.currentThread().getId() +" send success"+"\tsuccessCount:"+successCount);

                //将总线恢复到空闲状态（todo 这里可能会有并发问题）
                bus.setBusData(0);
//❎ 发送失败：在争用期内检测到碰撞。这时立即停止发送数据
            }else {


                System.out.println("ID:"+Thread.currentThread().getId() +" send collision"+"\tconflictCount:"+conflictCount);
                conflictCount--;
// 若重传达 16次仍不能成功，则停止重传而向上报错。
                if (conflictCount==0){
                    System.out.println("ID:"+Thread.currentThread().getId() +" send collision");
                    break;
                }else{
//  按规定发送人为干扰信号。执行指数退避算法，等待 r倍 512 比特时间后，返回到步骤 2️⃣，继续检测信道。
                    double backOffTime = (Math.pow(2, Math.min(10, 16 - conflictCount + 1)) - 1) * 5;
                        try {
                            Thread.sleep((long) backOffTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                }

            }
        }


    }

}
